package cn.codingstyle.exercise;

import lombok.SneakyThrows;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

public class Criteria {
    public static <T> Predicate<T> equal(String fieldName, Object value) {
        return obj -> {
            Object fieldValue = getFieldValue(obj, fieldName);
            if (fieldValue == null || value == null) {
                return fieldValue == value;
            }

            checkClassType(value, fieldName, fieldValue);
            return fieldValue.equals(value);
        };
    }

    private static void checkClassType(Object value, String fieldName, Object fieldValue) {
        if (!value.getClass().equals(fieldValue.getClass())) {
            throw new IllegalArgumentException(String.format("Method %s() returns %s can not compare with %s.", getGetter(fieldName), fieldValue.getClass().getSimpleName(), value.getClass().getSimpleName()));
        }
    }

    public static <T> Predicate<T> contains(String fieldName, String value) {
        return obj -> ((String) getFieldValue(obj, fieldName)).contains(value);
    }

    public static <T> Predicate<T> in(String fieldName, List<Object> values) {
        return obj -> {
            if (values.isEmpty()) {
                return false;
            }
            Object fieldValue = getFieldValue(obj, fieldName);
            checkClassType(values.get(0), fieldName, fieldValue);
            return values.contains(fieldValue);
        };
    }

    @SneakyThrows
    private static Object getFieldValue(Object obj, String fieldName) {
        String getter = getGetter(fieldName);

        try {
            Method method = obj.getClass().getMethod(getter);
            return method.invoke(obj);
        } catch (Exception e) {
            throw new IllegalArgumentException(String.format("Method %s not found in class %s.", getter, obj.getClass().getSimpleName()));
        }
    }

    private static String getGetter(String fieldName) {
        String firstLetter = fieldName.substring(0, 1).toUpperCase();
        return "get" + firstLetter + fieldName.substring(1);
    }
}
